// A simple program which interfaces to the W-Prolog engine.
// Author: Michael Winikoff (winikoff@cs.mu.oz.au)
// Date: 6/3/97
//
// Usage: java Go append.pl "append([1,2],X,[1,2,3,4])"
// 
// 
package ai.wprolog;

import java.io.IOException;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Vector;

import ai.wprolog.config.Configuration;
import ai.wprolog.essentials.Engine;
import ai.wprolog.essentials.ParseString;
import ai.wprolog.essentials.Term;
import ai.wprolog.essentials.TermList;
import ai.wprolog.exception.JPrologConfigException;
import ai.wprolog.exception.PrologSyntaxException;
import ai.wprolog.outmsg.Messenger;

import util.essential.SourceGetter;

public class JPrologController {
	// Constants:
	private final String PROLOG_FILE_EXTENSION = ".pl";
	private final String NEGATIVE_ANSWER = "No.";
	private final String LN = "\n";
	private final String PROMPT = "JPrologController> ";

	// Attributes:
	private String programName;
	private Messenger log;
	private LinkedList<String> facts;
	private LinkedList<String> rules;

	// Constructor:
	public JPrologController(String programName) throws IOException, Exception {
		if (!programName.endsWith(this.PROLOG_FILE_EXTENSION)) {
			programName += this.PROLOG_FILE_EXTENSION;
		}
		this.log = new Messenger();
		this.facts = new LinkedList<String>();
		this.rules = new LinkedList<String>();
		this.programName = programName;
		this.buildPLStructure(this.programName);
	}

	// Methods:
	/**
	 * Just for testing:
	 * 
	 * @param args
	 * @throws Exception
	 */
	static public void main(String args[]) throws Exception {
		String queryFileName = "test/append.qry", programFileName = "test/append.pl";
		Vector<String> queries = SourceGetter.config.getSource(queryFileName);
		JPrologController controller = new JPrologController(programFileName);
		System.out.println(controller.toString());
		System.out.println("=====================Program:");
		System.out.println(controller.toString());
		System.out.println("=====================");

		for (String query : queries) {
			System.out.println("=====================Queries:");
			System.out.println("Q: " + query);
			System.out.println("A: " + controller.stringQuery(query));
			System.out.println("=====================");
		}
		System.out.println("=====================LOG:");
		System.out.println(controller.getLog());
		System.out.println("=====================");
	}

	/**
	 * @param query
	 * @return whether query had one or more results.
	 * @throws Exception
	 */
	public boolean booleanQuery(String query) throws Exception {
		LinkedList<String> tmp = this.query(query);
		for (String string : tmp) {
			if (string.equals(this.NEGATIVE_ANSWER)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * @param query
	 * @return String representation of answers.
	 * @throws Exception
	 */
	public String stringQuery(String query) throws Exception {
		return SourceGetter.config.getStringRepresentation("", "; ", this
				.query(query));
	}

	/**
	 * @param query
	 * @return List of answers.
	 * @throws Exception
	 */
	public LinkedList<String> query(String query) throws Exception {
		LinkedList<String> ans = new LinkedList<String>();
		Term t = new Term(new ParseString(query, this.log));
		Engine eng = new Engine(t, ParseString.consult(this.toString(),
				new Hashtable<String, TermList>(), this.log));
		String tmp = eng.run();
		ans.add(tmp);
		do {
			tmp = eng.more();
			if (!tmp.endsWith(NEGATIVE_ANSWER)) {
				ans.add(tmp);
			}
		} while (!tmp.endsWith(NEGATIVE_ANSWER));
		return ans;
	}

	/**
	 * @param term
	 * @return whether were possible add the given term.
	 * @throws Exception
	 */
	public void addTerm(String term) throws IOException, PrologSyntaxException {
		SourceGetter.xmlManager.addXMLDocument(Configuration.CONFIG_FILE_NAME);
		String path = "config.syntax";
		String factsRegEx = SourceGetter.xmlManager.getAttributeValue(path,
				"factsregex"), rulesRegEx = SourceGetter.xmlManager
				.getAttributeValue(path, "rulesregex");
		if (factsRegEx != null && rulesRegEx != null) {
			SourceGetter.out.debug(this.PROMPT + "[addTerm] ",
					"factsRegEx: |" + factsRegEx + "|");
			SourceGetter.out.debug(this.PROMPT + "[addTerm] ",
					"rulesRegEx: |" + rulesRegEx + "|");
			SourceGetter.out.debug(this.PROMPT + "[addTerm] ",
					"\tline: |" + term + "|");
			if (term.matches(factsRegEx)) {
				this.facts.add(term);
			} else if (term.matches(rulesRegEx)) {
				this.rules.add(term);
			}else{
				throw new PrologSyntaxException("Wrong term \""+term+"\"!");
			}
		}
	}

	/**
	 * @return log data.
	 */
	public String getLog() {
		return log.toString();
	}

	/**
	 * @return name of program.
	 */
	public String getProgramName() {
		return programName;
	}

	/**
	 * This method allows to load PROLOG program into both facts and rules
	 * lists.
	 * 
	 * @param fileName
	 * @throws IOException
	 * @throws PrologSyntaxException
	 */
	private void buildPLStructure(String fileName) throws IOException,
			PrologSyntaxException {
		try {
			Vector<String> source = SourceGetter.config.getSource(fileName);
			String program = SourceGetter.config
					.getStringRepresentation(source);
			ParseString.consult(program, new Hashtable<String, TermList>(),
					this.log);
			SourceGetter.out.debug(this.PROMPT + "[buildPLStructure] ",
					"Program:\n|" + program + "|");
			SourceGetter.out
					.debug("-----------------------------------------------");
			SourceGetter.xmlManager
					.addXMLDocument(Configuration.CONFIG_FILE_NAME);
			String path = "config.syntax";
			String factsRegEx = SourceGetter.xmlManager.getAttributeValue(path,
					"factsregex"), rulesRegEx = SourceGetter.xmlManager
					.getAttributeValue(path, "rulesregex");
			if (factsRegEx != null && rulesRegEx != null) {
				SourceGetter.out.debug(this.PROMPT + "[buildPLStructure] ",
						"factsRegEx: |" + factsRegEx + "|");
				SourceGetter.out.debug(this.PROMPT + "[buildPLStructure] ",
						"rulesRegEx: |" + rulesRegEx + "|");
				for (String line : source) {
					SourceGetter.out.debug(this.PROMPT + "[buildPLStructure] ",
							"\tline: |" + line + "|");
					if (line.matches(factsRegEx)) {
						this.facts.add(line);
					} else if (line.matches(rulesRegEx)) {
						this.rules.add(line);
					}/*
					 * else{ throw new
					 * SyntaxPLException("Error at line \""+line+"\"!"); }
					 */
				}
			} else {
				throw new JPrologConfigException(
						"Facts RegEx and Rules RegEx not found at xml config file!");
			}
		} catch (Exception e) {
			// XXX: "e" already comes with an error description!
			throw new PrologSyntaxException("Can't parse program!\n" + e.toString());
		}
	}

	@Override
	public String toString() {
		StringBuffer buff = new StringBuffer();
		buff.append("% Facts:" + this.LN);
		for (String fact : this.facts) {
			buff.append(fact + this.LN);
		}
		buff.append("% Rules:" + this.LN);
		for (String rule : this.rules) {
			buff.append(rule + this.LN);
		}
		return buff.toString();
	}
}